package com.summer.boss.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;


public class RedisUtil {

    public static Logger LOGGER = LoggerFactory.getLogger(RedisUtil.class);

    private static StringRedisTemplate REDIS_TEMPLATE;
    private static final DefaultRedisScript<Integer> UNLOCK_SCRIPT = new DefaultRedisScript<>();
    private static final long DEFAULT_EXPIRE_TIME = 10;
    private static final long DEFAULT_WAIT_TIME = 200;
    private static final int DEFAULT_TRY_COUNT = 3;

    static {
//        UNLOCK_SCRIPT.setResultType(Integer.class);
//        unlockScript.setScriptText(
//                "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
//                "    return redis.call(\"del\",KEYS[1])\n" +
//                "else\n" +
//                "    return 0\n" +
//                "end"
//        );
        UNLOCK_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/unlock.lua")));
    }

    public static void init(StringRedisTemplate stringRedisTemplate) {
        RedisUtil.REDIS_TEMPLATE = stringRedisTemplate;
    }

    // --------------------------- 字符串操作 ---------------------------

    /**
     * 设置字符串键值对
     *
     * @param key   键
     * @param value 值
     */
    public static void set(String key, String value) {
        REDIS_TEMPLATE.opsForValue().set(key, value);
    }

    /**
     * 设置字符串键值对，并设置过期时间
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间
     * @param unit    时间单位
     */
    public static void set(String key, String value, long timeout, TimeUnit unit) {
        REDIS_TEMPLATE.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 获取字符串值
     *
     * @param key 键
     * @return 值
     */
    public static String get(String key) {
        return REDIS_TEMPLATE.opsForValue().get(key);
    }

    /**
     * 删除键
     *
     * @param key 键
     * @return 是否删除成功
     */
    public static boolean delete(String key) {
        return Boolean.TRUE.equals(REDIS_TEMPLATE.delete(key));
    }

    /**
     * 判断键是否存在
     *
     * @param key 键
     * @return 是否存在
     */
    public static boolean hasKey(String key) {
        return Boolean.TRUE.equals(REDIS_TEMPLATE.hasKey(key));
    }

    /**
     * 设置键的过期时间
     *
     * @param key     键
     * @param timeout 过期时间
     * @param unit    时间单位
     * @return 是否设置成功
     */
    public static boolean expire(String key, long timeout, TimeUnit unit) {
        return Boolean.TRUE.equals(REDIS_TEMPLATE.expire(key, timeout, unit));
    }

    // --------------------------- 哈希操作 ---------------------------

    /**
     * 设置哈希字段值
     *
     * @param key   键
     * @param field 字段
     * @param value 值
     */
    public static void hSet(String key, String field, String value) {
        REDIS_TEMPLATE.opsForHash().put(key, field, value);
    }

    /**
     * 获取哈希字段值
     *
     * @param key   键
     * @param field 字段
     * @return 值
     */
    public static String hGet(String key, String field) {
        return (String) REDIS_TEMPLATE.opsForHash().get(key, field);
    }

    /**
     * 获取哈希所有字段和值
     *
     * @param key 键
     * @return 字段和值的映射
     */
    public static Map<Object, Object> hGetAll(String key) {
        return REDIS_TEMPLATE.opsForHash().entries(key);
    }

    /**
     * 删除哈希字段
     *
     * @param key    键
     * @param fields 字段
     * @return 删除的字段数量
     */
    public static Long hDelete(String key, Object... fields) {
        return REDIS_TEMPLATE.opsForHash().delete(key, fields);
    }

    // --------------------------- 列表操作 ---------------------------

    /**
     * 向列表头部插入元素
     *
     * @param key   键
     * @param value 值
     * @return 列表长度
     */
    public static Long lPush(String key, String value) {
        return REDIS_TEMPLATE.opsForList().leftPush(key, value);
    }

    /**
     * 向列表尾部插入元素
     *
     * @param key   键
     * @param value 值
     * @return 列表长度
     */
    public static Long rPush(String key, String value) {
        return REDIS_TEMPLATE.opsForList().rightPush(key, value);
    }

    /**
     * 获取列表范围元素
     *
     * @param key   键
     * @param start 起始索引
     * @param end   结束索引
     * @return 元素列表
     */
    public static List<String> lRange(String key, long start, long end) {
        return REDIS_TEMPLATE.opsForList().range(key, start, end);
    }

    // --------------------------- 集合操作 ---------------------------

    /**
     * 向集合添加元素
     *
     * @param key    键
     * @param values 值
     * @return 添加成功的元素数量
     */
    public static Long sAdd(String key, String... values) {
        return REDIS_TEMPLATE.opsForSet().add(key, values);
    }

    /**
     * 获取集合所有元素
     *
     * @param key 键
     * @return 元素集合
     */
    public static Set<String> sMembers(String key) {
        return REDIS_TEMPLATE.opsForSet().members(key);
    }

    /**
     * 判断元素是否在集合中
     *
     * @param key   键
     * @param value 值
     * @return 是否存在
     */
    public static boolean sIsMember(String key, String value) {
        return Boolean.TRUE.equals(REDIS_TEMPLATE.opsForSet().isMember(key, value));
    }

    // --------------------------- 有序集合操作 ---------------------------

    /**
     * 向有序集合添加元素
     *
     * @param key   键
     * @param value 值
     * @param score 分数
     * @return 是否添加成功
     */
    public static Boolean zAdd(String key, String value, double score) {
        return REDIS_TEMPLATE.opsForZSet().add(key, value, score);
    }

    /**
     * 获取有序集合范围元素
     *
     * @param key 键
     * @param min 最小分数
     * @param max 最大分数
     * @return 元素集合
     */
    public static Set<String> zRangeByScore(String key, double min, double max) {
        return REDIS_TEMPLATE.opsForZSet().rangeByScore(key, min, max);
    }

    // 以下是分布式锁相关方法

    /**
     * @param lockKey 锁名称
     * @param requestId 请求标识（确保解锁时是同一个客户端）
     * 锁过期时间10s
     */
    public static boolean lock(String lockKey, String requestId) {
        return lock(lockKey, requestId, DEFAULT_EXPIRE_TIME);
    }

    public static boolean lock(String lockKey, String requestId, long expireTime) {
        return lock(lockKey, requestId, expireTime, TimeUnit.SECONDS);
    }

    public static boolean lock(String lockKey, String requestId, long expireTime, TimeUnit unit ) {
        Boolean success = REDIS_TEMPLATE.opsForValue().setIfAbsent(lockKey, requestId, expireTime, unit);
        return success != null && success;
    }

    /**
     * @param lockKey 锁名称
     * @param requestId 请求标识（确保解锁时是同一个客户端）
     * 锁过期时间10s, 首次尝试间隔200ms, 尝试3次获取锁
     */
    public static boolean tryLock(String lockKey, String requestId) {
        return tryLock(lockKey, requestId, DEFAULT_EXPIRE_TIME);
    }

    /**
     * @param lockKey 锁名称
     * @param requestId 请求标识（确保解锁时是同一个客户端）
     * @param expireTime 锁过期时间
     * 首次尝试间隔200ms, 尝试3次获取锁
     */
    public static boolean tryLock(String lockKey, String requestId, long expireTime) {
        return tryLock(lockKey, requestId, expireTime, DEFAULT_WAIT_TIME, TimeUnit.SECONDS, DEFAULT_TRY_COUNT);
    }

    /**
     * @param lockKey 锁名称
     * @param requestId 请求标识（确保解锁时是同一个客户端）
     * @param expireTime 锁过期时间
     * @param waitTime 尝试间隔,单位毫秒
     * @param unit 时间单位
     * @param count 尝试次数
     */
    public static boolean tryLock(String lockKey, String requestId, long expireTime, long waitTime, TimeUnit unit, int count) {
        // 尝试获取锁操作
        while (count > 0) {
            Boolean success = REDIS_TEMPLATE.opsForValue().setIfAbsent(lockKey, requestId, expireTime, unit);
            if (success != null && success) {
                return true;
            } else {
                // 每次请求等待一段时间
                try {
                    Thread.sleep(waitTime);
                    waitTime = 2 * waitTime;
                } catch (InterruptedException ex) {
                    LOGGER.error("线程[{}]尝试获取锁[{}]失败", Thread.currentThread().getName(), lockKey, ex);
                }
            }
            count --;
        }
        return false;
    }

    /**
     * redis可以保证lua中的键的原子操作 unlock:lock调用完之后需unlock,否则需等待lock自动过期
     * @param requestId 请求标识（确保解锁时是同一个客户端）
     * @param lockKey 分布式锁 key
     */
    public static void unlock( String lockKey, String requestId) {
        REDIS_TEMPLATE.execute(UNLOCK_SCRIPT, Collections.singletonList(lockKey), requestId);
    }
}
